﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Collections;
using System.Threading;

using UnityEngine;
using System.IO;
using System.Security.Cryptography;

namespace DashFire
{
  internal class ResUpdateTool
  {
    private static Dictionary<string, ResAsyncInfo> s_AsyncLoadAssetbundleList = new Dictionary<string, ResAsyncInfo>();

    public static ResAsyncInfo StartCoroutineImpl(IEnumerator routine)
    {
      ResAsyncInfo info = new ResAsyncInfo();
      info.CurCoroutine = CoroutineProxy.Instance.StartCoroutine(routine);
      return info;
    }
    public static bool SearchUpgradeRes(List<string> container)
    {
      Dictionary<string, ResVersionData> resVersinList = ResVersionProvider.Instance.GetData();
      if (resVersinList != null && resVersinList.Count > 0) {
        foreach (ResVersionData rv in resVersinList.Values) {
          if (ResLoadHelper.IsResNeedUpdate(rv.m_AssetBundleName, rv.m_MD5)) {
            container.Add(rv.m_AssetBundleName);
          }
        }
      }
      return true;
    }
    public static bool SearchInnerRes(List<string> container)
    {
      Dictionary<string, ResVersionData> resVersinList = ResVersionProvider.Instance.GetData();
      if (resVersinList != null && resVersinList.Count > 0) {
        foreach (ResVersionData rv in resVersinList.Values) {
          if (rv != null) {
            container.Add(rv.m_AssetBundleName);
          }
        }
      }
      return true;
    }
    public static bool SaveCacheAB(byte[] bytes, ResVersionData data, bool isUnzip)
    {
      try {
        if (bytes == null) {
          ResLoadHelper.Log("SaveCacheAB bytes null or empty data;" + data.m_AssetBundleName);
          return false;
        }
        string persistPath = ResLoadHelper.GetFilePath(ResPathType.Cache, data.m_AssetBundleName);
        if (!string.IsNullOrEmpty(persistPath)) {
          string dir = Path.GetDirectoryName(persistPath);
          if (!Directory.Exists(dir)) {
            Directory.CreateDirectory(dir);
          }
          if (isUnzip) {
            ZipHelper.UnzipFile(bytes, persistPath);
          } else {
            File.WriteAllBytes(persistPath, bytes);
          }
#if UNITY_APPSTORE && UNITY_IPHONE
          iPhone.SetNoBackupFlag(persistPath);
#endif
          ClientResVersionProvider.Instance.AddData(data);
          ResUpdateControler.s_DownLoadNum++;
        }
      } catch (System.Exception ex) {
        ResLoadHelper.Log("SaveCacheAB abName:" + data.m_AssetBundleName + "ex:" + ex);
        return false;
      }
      return true;
    }
    public static bool SaveResSheetAB(byte[] bytes, string abName, bool isUnzip)
    {
      try {
        if (bytes == null) {
          ResLoadHelper.Log("SaveResSheetAB bytes null or empty data;" + abName);
          return false;
        }
        string persistPath = ResLoadHelper.GetFilePath(ResPathType.Cache, abName);
        if (!string.IsNullOrEmpty(persistPath)) {
          string dir = Path.GetDirectoryName(persistPath);
          if (!Directory.Exists(dir)) {
            Directory.CreateDirectory(dir);
          }
          if (isUnzip) {
            ZipHelper.UnzipFile(bytes, persistPath);
          } else {
            File.WriteAllBytes(persistPath, bytes);
          }
#if UNITY_APPSTORE && UNITY_IPHONE
          iPhone.SetNoBackupFlag(persistPath);
#endif
        }
      } catch (System.Exception ex) {
        ResLoadHelper.Log("SaveCacheAB abName:" + abName + "ex:" + ex);
        return false;
      }
      return true;
    }
    public static IEnumerator ExtractResSheet(ResAsyncInfo info, AssetBundle assetBundle, bool isHideLoading = false)
    {
      if (assetBundle == null) {
        info.IsError = true;
        yield break;
      }

      string outPath = ResLoadHelper.Combine(Application.persistentDataPath, ResUpdateConfig.s_ResSheetCachePath);
      if (!Directory.Exists(outPath)) {
        Directory.CreateDirectory(outPath);
      }

      TextAsset txt = assetBundle.LoadAsset(ResUpdateConfig.s_ResSheetFile, typeof(UnityEngine.TextAsset)) as TextAsset;
      if (txt != null && txt.bytes != null && txt.bytes.Length > 0) {

        string sheetListFilePath = ResLoadHelper.Combine(outPath, ResUpdateConfig.s_ResSheetFile);
        try {
          File.WriteAllBytes(sheetListFilePath, txt.bytes);
#if UNITY_APPSTORE && UNITY_IPHONE
          iPhone.SetNoBackupFlag(sheetListFilePath);
#endif
        } catch (System.Exception ex) {
          ResLoadHelper.Log(string.Format("ExtractResSheet write file :{0} ex:{1} st:{2}", sheetListFilePath, ex.Message, ex.StackTrace));
          ResUpdateControler.s_UpdateError = ResUpdateError.LoadResSheet_Extract_Error;
          info.IsError = true;
          yield break;
        }

        using (MemoryStream ms = new MemoryStream(txt.bytes)) {
          ms.Seek(0, SeekOrigin.Begin);
          System.Text.Encoding encoding = System.Text.Encoding.UTF8;
          using (StreamReader sr = new StreamReader(ms, encoding)) {
            int totalNum = int.Parse(sr.ReadLine());
            for (int sheetIndex = 1; sheetIndex <= totalNum; sheetIndex++) {
              string sheetFile = sr.ReadLine();
              if (!string.IsNullOrEmpty(sheetFile.Trim())) {
                TextAsset asset = assetBundle.LoadAsset(sheetFile, typeof(UnityEngine.TextAsset)) as TextAsset;
                if (asset != null && asset.bytes != null && asset.bytes.Length > 0) {
                  try {
                    string sheetFilePath = ResLoadHelper.Combine(outPath, sheetFile);
                    string dir = Path.GetDirectoryName(sheetFilePath);
                    if (!Directory.Exists(dir)) {
                      Directory.CreateDirectory(dir);
                    }
                    File.WriteAllBytes(sheetFilePath, asset.bytes);
#if UNITY_APPSTORE && UNITY_IPHONE
                    iPhone.SetNoBackupFlag(sheetFilePath);
#endif
                  } catch (System.Exception ex) {
                    ResLoadHelper.Log(string.Format("ExtractResSheet skip file :{0} ex:{1} st:{2}",
                      sheetFile, ex.Message, ex.StackTrace));
                    info.IsError = true;
                    ResUpdateControler.s_UpdateError = ResUpdateError.LoadResSheet_Extract_Error;
                    if (assetBundle != null) {
                      assetBundle.Unload(false);
                    }
                    yield break;
                  }
                }
              } else {
                ResLoadHelper.Log("ExtractResSheet skip file:" + sheetFile);
              }
              if (!isHideLoading && sheetIndex % 10 == 0) {
                LogicForGfxThread.UpdateLoadingProgress(0.5f + 0.5f * sheetIndex / totalNum);
                yield return 1;
              }
            }
          }
        }
      }
      if (assetBundle != null) {
        assetBundle.Unload(false);
      }
      info.IsDone = true;
      info.Progress = 1.0f;
    }

    internal static IEnumerator LoadAssetBundleImplAsync(ResAsyncInfo info, string abUrl, MyAction<UnityEngine.Object> callback)
    {
      AssetBundleCreateRequest operation = null;
      try
      {
        operation  = AssetBundle.LoadFromFileAsync(abUrl);
      }
      catch (System.Exception ex)
      {
        callback(null);
        UnityEngine.Debug.Log("LoadAssetBundle failed abUrl:" + abUrl + " ex:" + ex);
        yield break;
      }

      while (true)
      {
        if (operation == null || operation.isDone)
        {
          break;
        }
        else
        {
          info.Progress = operation.progress;
          yield return 1;
        }
      }

      if (operation == null || operation.assetBundle == null)
      {
        UnityEngine.Debug.Log("LoadAssetBundle null url:" + abUrl);
        ResUpdateControler.s_UpdateError = ResUpdateError.Create_Assetbundle_Error;
        callback(null);
        yield break;
      }

      info.IsDone = false;
      info.Progress = 1.0f;
      info.Operation = operation;
      callback(operation.assetBundle);
    }
    internal static ResAsyncInfo LoadAssetBundleAsync(string abUrl, MyAction<UnityEngine.Object> callback)
    {
      ResAsyncInfo info = new ResAsyncInfo();

      ResAsyncInfo tryGetValue = null;
      if (s_AsyncLoadAssetbundleList.TryGetValue(abUrl, out tryGetValue))
      {
        UnityEngine.Debug.Log("LoadAssetBundleAsync repeated url:" + abUrl);
        ResUpdateControler.s_UpdateError = ResUpdateError.Create_Assetbundle_Error;
        return ResAsyncInfo.EmptyAsyncInfo;
      }
      s_AsyncLoadAssetbundleList.Add(abUrl, info);

      info.CurCoroutine = CoroutineProxy.Instance.StartCoroutine(LoadAssetBundleImplAsync(info, abUrl, (asset) =>
      {
        callback(asset);
        s_AsyncLoadAssetbundleList.Remove(abUrl);
      }));
      return info;
    }

    public static ResAsyncInfo LoadCachedAssetAsync(string resName, MyAction<UnityEngine.Object> callback)
    {
      DFProfiler.Start("LoadCachedAssetAsync");

      AssetBundle assetBundle = null;
      ClientResVersionData resVersion = null;
      try
      {
        resVersion = ResLoadHelper.GetResVersionDataByAssetName(resName);
        if (resVersion != null && ResLoadHelper.IsResAssetbundleCached(resVersion.m_AssetBundleName))
        {
          if (ResUpdateControler.s_SharedAssetbundle.ContainsKey(resVersion.m_AssetBundleName))
          {
            assetBundle = ResUpdateControler.s_SharedAssetbundle[resVersion.m_AssetBundleName];
            if (assetBundle != null)
            {
              UnityEngine.Object asset = assetBundle.LoadAsset(resVersion.m_AssetName);
              callback(asset);

              DFProfiler.Stop("LoadCachedAssetAsync");
              ResAsyncInfo info = new ResAsyncInfo(true);
              info.Result = asset;
              return info;
            }
          }
          else
          {
            List<string> needLoadUrllist = new List<string>();
            if (!string.IsNullOrEmpty(resVersion.m_Dependency))
            {
              string[] dependency = resVersion.m_Dependency.Split(new string[] { ";", ":" }, StringSplitOptions.RemoveEmptyEntries);
              if (dependency != null && dependency.Length > 0)
              {
                foreach (string curDependGuid in dependency)
                {
                  if (!ResUpdateControler.s_SharedAssetbundle.ContainsKey(curDependGuid))
                  {
                    needLoadUrllist.Add(curDependGuid);
                  }
                }
              }
            }

            needLoadUrllist.Add(resVersion.m_AssetBundleName);

            ResAsyncInfo info = ResAsyncInfo.EmptyAsyncInfo;
            int asyncLoadCount = 0;
            for (int index = 0; index < needLoadUrllist.Count; index++)
            {
              info = ResUpdateTool.LoadAssetBundleAsync(ResLoadHelper.GetFilePath(ResPathType.Cache,needLoadUrllist[index]), (bundle) =>
              {
                asyncLoadCount++;
                if (bundle != null)
                {
                  ResUpdateControler.s_SharedAssetbundle.Add(needLoadUrllist[index], bundle as AssetBundle);
                }
                if (asyncLoadCount == needLoadUrllist.Count /*&& index == (needLoadUrllist.Count - 1)*/)
                {
                  DFProfiler.Start("LoadCachedAssetAsync_LoadAsset");

                  UnityEngine.Object asset = null;
                  if (bundle != null)
                  {
                    asset = (bundle as AssetBundle).LoadAsset(resVersion.m_AssetName);
                  }
                  info.Result = asset;
                  callback(asset);

                  DFProfiler.Stop("LoadCachedAssetAsync_LoadAsset");
                }
                info.IsDone = true;
              });
            }

            DFProfiler.Stop("LoadCachedAssetAsync");
            return info;
          }
        }
        
      }
      catch (System.Exception ex)
      {
        ResLoadHelper.Log(string.Format("LoadLevelImplAsync Exception:{0}/{1}", ex.Message, ex.StackTrace));
      }

      DFProfiler.Stop("LoadCachedAssetAsync");

      return ResAsyncInfo.EmptyAsyncInfo;
    }

    public static AssetBundle LoadAssetBundle(string abUrl)
    {
      AssetBundle assetBundle = null;
      try {
        ResAsyncInfo tryGetValue = null;
        if (s_AsyncLoadAssetbundleList.TryGetValue(abUrl, out tryGetValue))
        {
          while (true)
          {
            if (tryGetValue.IsError || tryGetValue.IsDone)
            {
              break;
            }
            Thread.Sleep(1);
          }

          if (tryGetValue.IsDone)
          {
            AssetBundleCreateRequest operation = tryGetValue.Operation as AssetBundleCreateRequest;
            return operation.assetBundle;
          }
          else
          {
            UnityEngine.Debug.Log("LoadAssetBundle failed By wait AsyncLoad abUrl:" + abUrl);
            return null;
          }
        }

        //UnityEngine.Debug.Log("LoadAssetBundle ab abUrl:" + abUrl);
        assetBundle = AssetBundle.LoadFromFile(abUrl);
        if (assetBundle == null) {
          UnityEngine.Debug.Log("LoadAssetBundle null url:" + abUrl);
          ResUpdateControler.s_UpdateError = ResUpdateError.Create_Assetbundle_Error;
          return null;
        }
      } catch (System.Exception ex) {
        UnityEngine.Debug.Log("LoadAssetBundle failed abUrl:" + abUrl + " ex:" + ex);
        return null;
      }
      return assetBundle;
    }
    public static UnityEngine.Object LoadCachedAsset(string resName)
    {
      UnityEngine.Object asset = null;

      AssetBundle assetBundle = null;
      ClientResVersionData resVersion = null;
      try {
        resVersion = ResLoadHelper.GetResVersionDataByAssetName(resName);
        if (resVersion != null && ResLoadHelper.IsResAssetbundleCached(resVersion.m_AssetBundleName)) {
          if (ResUpdateControler.s_SharedAssetbundle.ContainsKey(resVersion.m_AssetBundleName)) {
            assetBundle = ResUpdateControler.s_SharedAssetbundle[resVersion.m_AssetBundleName];
          } else {
            if (!string.IsNullOrEmpty(resVersion.m_Dependency)) {
              string[] dependency = resVersion.m_Dependency.Split(new string[] { ";", ":" }, StringSplitOptions.RemoveEmptyEntries);
              if (dependency != null && dependency.Length > 0) {
                foreach (string curDependGuid in dependency) {
                  if (!ResUpdateControler.s_SharedAssetbundle.ContainsKey(curDependGuid)) {
                    string assetBundleDependencyUrl = ResLoadHelper.GetFilePath(ResPathType.Cache, curDependGuid);
                    assetBundle = ResUpdateTool.LoadAssetBundle(assetBundleDependencyUrl);
                    if (assetBundle != null) {
                      ResUpdateControler.s_SharedAssetbundle.Add(curDependGuid, assetBundle);
                    }
                  }
                }
              }
            }

            string assetBundleUrl = ResLoadHelper.GetFilePath(ResPathType.Cache, resVersion.m_AssetBundleName);
            assetBundle = ResUpdateTool.LoadAssetBundle(assetBundleUrl);
            if (assetBundle != null) {
              ResUpdateControler.s_SharedAssetbundle.Add(resVersion.m_AssetBundleName, assetBundle);
            }
          }
        }
        if (assetBundle != null) {
          asset = assetBundle.LoadAsset(resVersion.m_AssetName);
        }
      } catch (System.Exception ex) {
        ResLoadHelper.Log(string.Format("LoadLevelImplAsync Exception:{0}/{1}", ex.Message, ex.StackTrace));
        asset = null;
      }
      return asset;
    }
    public static string FormatSpeed(double speed)
    {
      string retSpeed = string.Empty;
      if (speed < 1024) {
        retSpeed = string.Format("{0:N0}B/秒", speed);
      } else if (speed < 1024 * 1024) {
        retSpeed = string.Format("{0:N0}KB/秒", speed / 1024);
      } else {
        retSpeed = string.Format("{0:N0}M/秒", speed / (1024 * 1024));
      }
      return retSpeed;
    }
    public static string FormatNumber(double num)
    {
      return string.Format("{0:N0}", num);
    }
  }
}